/**
 * Viewer2DSearchTool.java Created on : 26 juin 07
 */
package fr.limsi.seven;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.util.Iterator;
import java.util.Vector;
import java.util.Map.Entry;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JToolBar;

/**
 * @author Camille
 */
public class Pyramide2DSearchTool {

	/**
	 * Fog
	 */
	public static final Color FOG = new Color(0.5f, 0.5f, 0.5f, 0.8f);

	/**
	 * Search field witdth
	 */
	public static final int SEARCH_FIELD_WIDTH = 300;

	/**
	 * Search field height
	 */
	public static final int SEARCH_FIELD_HEIGHT = 20;

	/**
	 * Spaces between search bar elements
	 */
	public static final int SPACES = 30;

	/**
	 * Search bar margins
	 */
	public static final int MARGINS = 10;

	/**
	 * Focus ellipses margins
	 */
	public static final int FOCUS_MARGINS = 5;

	/**
	 * Associated {@link Pyramide2D}
	 */
	protected Pyramide2D pyramide2D;

	/**
	 * {@link Area} with elliptic clipping zones on focused nodes
	 */
	protected Area focusArea;

	/**
	 * {@link Vector} of all {@link Node2D} matching the search
	 */
	protected Vector<Node2D> matchingNodes;

	/**
	 * The search bar panel
	 */
	protected JToolBar searchBar;

	/**
	 * The search field
	 */
	protected JTextField searchField;

	/**
	 * Search criteria check box
	 */
	protected JCheckBox searchLabel;

	/**
	 * Search criteria check box
	 */
	protected JCheckBox searchName;

	/**
	 * Search criteria check box
	 */
	protected JCheckBox searchID;

	/**
	 * Searching indicator
	 */
	protected boolean searching;

	/**
	 * Lock for the <i>matchingNodes</i> {@link Vector}
	 */
	protected boolean listLocked;

	/**
	 * Lock for the <i>focusArea</i> {@link Area}
	 */
	protected boolean areaLocked;

	/**
	 * Default constructor
	 * 
	 * @param pyramide2D
	 *            the associated {@link Pyramide2D}
	 */
	public Pyramide2DSearchTool(Pyramide2D pyramide2D) {

		this.pyramide2D = pyramide2D;

		focusArea = new Area();
		matchingNodes = new Vector<Node2D>();
		searching = false;
		listLocked = false;
		areaLocked = false;

		buildSearchBar();
		buildInteractions();
	}

	/**
	 * Builds the search bar and all its components
	 */
	protected void buildSearchBar() {
		searchBar = new JToolBar();
		searchBar.setFloatable(false);
		searchBar.setLayout(new BoxLayout(searchBar, BoxLayout.LINE_AXIS));
		searchBar.setAlignmentX(Component.LEFT_ALIGNMENT);

		searchField = new JTextField();
		searchField.setAlignmentX(Component.LEFT_ALIGNMENT);
		searchField.setPreferredSize(new Dimension(SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT));
		searchField.setMinimumSize(new Dimension(SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT));
		searchField.setMaximumSize(new Dimension(SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT));

		searchName = new JCheckBox("Nom");
		searchName.setOpaque(false);
		searchName.setAlignmentX(Component.LEFT_ALIGNMENT);
		searchName.setSelected(true);

		searchLabel = new JCheckBox("Label");
		searchLabel.setOpaque(false);
		searchLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
		searchLabel.setSelected(true);

		searchID = new JCheckBox("ID");
		searchID.setOpaque(false);
		searchID.setAlignmentX(Component.LEFT_ALIGNMENT);
		searchID.setSelected(true);

		searchBar.add(Box.createRigidArea(new Dimension(MARGINS, 0)));
		searchBar.add(new JLabel(new ImageIcon(getClass().getResource("/fr/limsi/seven/images/search.png"))));
		searchBar.add(Box.createRigidArea(new Dimension(MARGINS, 0)));
		searchBar.add(searchField);
		searchBar.add(Box.createRigidArea(new Dimension(SPACES, 0)));
		searchBar.add(searchName);
		searchBar.add(Box.createRigidArea(new Dimension(SPACES, 0)));
		searchBar.add(searchLabel);
		searchBar.add(Box.createRigidArea(new Dimension(SPACES, 0)));
		searchBar.add(searchID);
	}

	/**
	 * Builds all search bar interactions
	 */
	protected void buildInteractions() {

		FocusAdapter focusListener = new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				searching = false;
			}
		};

		searchField.addFocusListener(focusListener);

		KeyAdapter searchListener = new KeyAdapter() {
			@Override
			public void keyReleased(KeyEvent e) {

				searching = true;

				new Thread() {
					@Override
					public void run() {
						performSearch();
						pyramide2D.repaint();
					}
				}.start();
			}
		};

		searchField.addKeyListener(searchListener);

		MouseAdapter clickListener = new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				new Thread() {
					@Override
					public void run() {
						performSearch();
						pyramide2D.repaint();
					}
				}.start();
			}
		};

		searchName.addMouseListener(clickListener);
		searchLabel.addMouseListener(clickListener);
		searchID.addMouseListener(clickListener);
	}

	/**
	 * Fills the <i>matchingNodes</i> {@link Vector}
	 */
	protected void performSearch() {

		lockList();

		matchingNodes.clear();

		for (Iterator<Entry<Integer, Node2D>> entries = pyramide2D.getNodes().entrySet().iterator(); entries.hasNext();) {

			Entry<Integer, Node2D> entry = entries.next();
			Node2D node2D = entry.getValue();

			if ((searchName.isSelected() && !searchField.getText().equals("") && node2D.getNodeName().toLowerCase().contains(searchField.getText().toLowerCase())) || (searchLabel.isSelected() && !searchField.getText().equals("") && node2D.getUserLabel().getText().toLowerCase().contains(searchField.getText().toLowerCase())) || (searchID.isSelected() && !searchField.getText().equals("") && Integer.toString(node2D.getID()).toLowerCase().contains(searchField.getText().toLowerCase()))) {
				matchingNodes.add(node2D);
			}
		}

		unlockList();
	}

	/**
	 * Draws the focus area on the associated {@link Pyramide2D} (i.e. a gray rectangle with elliptic holes on the search matching node positions)
	 * 
	 * @param g2
	 */
	protected void drawFocusArea(Graphics2D g2) {

		lockArea();

		if (searching) {

			updateArea();

			Shape oldClip = g2.getClip();

			g2.setClip(focusArea);
			g2.setColor(FOG);
			g2.fill(focusArea);

			g2.setClip(oldClip);
		}

		unlockArea();
	}

	/**
	 * Updates the focusArea {@link Area}
	 */
	protected void updateArea() {

		lockList();

		focusArea = new Area(new Rectangle.Float(0, 0, pyramide2D.getWidth(), pyramide2D.getHeight()));

		for (Node2D node2D : matchingNodes) {
			focusArea.subtract(new Area(new Ellipse2D.Float(node2D.getX() - FOCUS_MARGINS, node2D.getY() - FOCUS_MARGINS, node2D.getWidth() + (2 * FOCUS_MARGINS), node2D.getHeight() + (2 * FOCUS_MARGINS))));
		}

		unlockList();
	}

	/**
	 * Sets the lock on the <i>matchingNodes</i> {@link Vector}, if already set waits until unlock is called by the locking thread
	 */
	protected synchronized void lockList() {
		while (listLocked) {
			try {
				wait();
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		listLocked = true;
	}

	/**
	 * Release the lock on the <i>matchingNodes</i> {@link Vector}
	 */
	protected synchronized void unlockList() {
		listLocked = false;
		notify();
	}

	/**
	 * Sets the lock on the <i>focusArea</i> {@link Area}, if already set waits until unlock is called by the locking thread
	 */
	protected synchronized void lockArea() {
		while (areaLocked) {
			try {
				wait();
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		areaLocked = true;
	}

	/**
	 * Release the lock on the <i>focusArea</i> {@link Area}
	 */
	protected synchronized void unlockArea() {
		areaLocked = false;
		notify();
	}

	/**
	 * Getter for the tool <i>searchBar</i>
	 * 
	 * @return the tool search bar {@link JToolBar}
	 */
	public JToolBar getSearchBar() {
		return searchBar;
	}

	/**
	 * Getter for the tool searching state
	 * 
	 * @return true if currently in searching mode, or else false
	 */
	public boolean isSearching() {
		return searching;
	}

	/**
	 * Setter for the tool searching state
	 * 
	 * @param searching
	 *            the state to be set
	 */
	public void setSearching(boolean searching) {
		this.searching = searching;
	}
}
